using System;
using System.IO.Ports;
using System.Threading;
using Gadgeteer.Modules.Sytech.Camera;
using Microsoft.SPOT;


namespace Gadgeteer.Modules.Sytech.CameraProtocol
{
    /// <summary>
    /// Low level camera protocol for type 0706 cameras
    /// </summary>
    internal class Camera0706
    {

        private Gadgeteer.Interfaces.Serial  m_serialPort;


        private static readonly byte[] RESET = new byte[] { 0x56, 0x00, 0x26, 0x00 };
        private static readonly byte[] CAPTURE = new byte[] { 0x56, 0x00, 0x36, 0x01, 0x00 };
        private static readonly byte[] STOP = new byte[] { 0x56, 0x00, 0x36, 0x01, 0x03 };

        private static readonly byte[] COMPRESSION = new byte[] { 0x56, 0x00, 0x31, 0x05, 0x01, 0x01, 0x12, 0x04 };

        private static readonly byte[] IMAGESIZE = new byte[] { 0x56, 0x00, 0x31, 0x05, 0x04, 0x01, 0x00, 0x19 };
        private static readonly byte[] IMAGESIZERAM = new byte[] { 0x56, 0x00, 0x54, 0x01 };

        private static readonly byte[] POWERSAVE = new byte[] { 0x56, 0x00, 0x3E, 0x03, 0x00, 0x01 };
        private static readonly byte[] BAUDRATE = new byte[] { 0x56, 0x00, 0x24, 0x03, 0x01 };


        private static readonly byte[] FILE_SIZE = new byte[] { 0x56, 0x00, 0x34, 0x01, 0x00 };
        private static readonly byte[] READ_FILE = new byte[] { 0x56, 0x00, 0x32, 0x0C, 0x00, 0x0A, 00, 00, };

        private byte[] m_readBuffer;
     


        /// <summary>
        /// Camera Protocol contrustor
        /// </summary>
        /// <param name="cameraPort"></param>
        internal Camera0706(Gadgeteer.Interfaces.Serial cameraPort)
        {
            m_serialPort = cameraPort;
            m_readBuffer = new byte[100];

            m_serialPort.ReadTimeout = 3000; 
            m_serialPort.Open();
        }

        /// <summary>
        /// Flush the serial buffers
        /// </summary>
        internal void Flush()
        {
            m_serialPort.DiscardInBuffer();
        }

        /// <summary>
        /// Perform a camera reset
        /// </summary>
        /// <returns></returns>
        internal bool DoReset()
        {
            byte[] response = new byte[4];
            SendAndWaitForResponse(RESET, ref response);
           

            return true;
        }

        /// <summary>
        /// Start an image capture
        /// </summary>
        /// <returns></returns>
        internal bool CaptureImage()
        {
            byte[] response = new byte[5];
            SendAndWaitForResponse(CAPTURE, ref response);
            return true;
        }


        /// <summary>
        /// Set Image size in flash
        /// 
        /// This does not work reliably yet - so it is more
        /// convienient to use the ram version
        /// </summary>
        /// <param name="size"></param>
        /// <returns></returns>
        private bool SetImageSizeFlash(ImageSize size)
        {
            byte[] cmd = new byte[9];
            IMAGESIZE.CopyTo(cmd, 0);
            // need a reset or power up after size change
            string sizeString = string.Empty;
            switch (size)
            {
                case ImageSize.thumb:
                    cmd[8] = 0x22;
                    sizeString = "Thumb : 160 * 120";
                    break;
                case ImageSize.svga:
                    cmd[8] = 0x11;
                    sizeString = "Half Vga : 320 x 240";
                    break;
                case ImageSize.vga:
                    cmd[8] = 0x00;
                    sizeString = "Vga : 640 x 320";
                    break;
            }

            // seems to need a reset sent after command to trugger write
             SendCommand(cmd);
               SendCommand(RESET);

           //  now we need a reset to restart with new setting
            byte[] response = new byte[5];
            SendAndWaitForResponse(RESET, ref response);

            bool res = false;
            if (response[3] == 0 || response[4] == 0)
            {
                Debug.Print("Size Changed to " + sizeString);
                Debug.Print(response[0] + ":" +
                            response[1] + ":" +
                            response[2] + ":" +
                            response[3] + ":" +
                            response[4]);


             
                res = true;
            }
            else
            {
                Debug.Print("Size change failed");
            }
            return res;

            
        }

        /// <summary>
        /// Set the camera image size
        /// 
        /// Sets in RAM, will default to the 
        /// flash set value o start up
        /// </summary>
        /// <returns></returns>
        internal bool SetImageSize(ImageSize size)
        {
            
            byte[] cmd = new byte[5];
            IMAGESIZERAM.CopyTo(cmd, 0);
            // need a reset or power up after size change
            string sizeString = string.Empty;
            switch (size)
            {
                case ImageSize.thumb:
                    cmd[4] = 0x22;
                    sizeString = "Thumb : 160 * 120";
                    break;
                case ImageSize.svga:
                    cmd[4] = 0x11;
                    sizeString = "Half Vga : 320 x 240";
                    break;
                case ImageSize.vga:
                    cmd[4] = 0x00;
                    sizeString = "Vga : 640 x 320";
                    break;
            }
            byte[] response = new byte[5];
            SendAndWaitForResponse(cmd, ref response);

            // simple check on response
            bool res = false;
            if ( response[3] == 0 || response[4] == 0)
            {
                //Debug.Print("Size Changed to " + sizeString);
                //Debug.Print(response[0] + ":" +
                //            response[1] + ":" +
                //            response[2] + ":" +
                //            response[3] + ":" +
                //            response[4] );

                            
                res = true;
            }
            //else
            //{
            //     Debug.Print( "Size change failed");
            //}
            return res;

        }

        /// <summary>
        /// Read the cpature file size from the camera
        /// </summary>
        /// <returns></returns>
        internal int ReadFileSize()
        {
            byte[] response = new byte[9];
            SendAndWaitForResponse(FILE_SIZE, ref response);

            int filesize = 0;
            filesize = (response[7] << 8) + response[8];
            return filesize;

        }

        private bool StopCapture()
        {
            byte[] response = new byte[5];
            SendAndWaitForResponse(STOP, ref response);
            return true;
        }

        
        /// <summary>
        /// Picture progress event
        /// </summary>
        internal event PictureProgressDel OnProgress;

        /// <summary>
        /// Note: needs progress event adding after each block
        /// </summary>
        /// <param name="size"></param>
        /// <returns></returns>
        internal byte[] ReadBlockFile(int size)
        {
           // int blocksize = 512;
            int blocksize = 1024;

            int numberOfBlocks = size/blocksize;
            int blocks = numberOfBlocks;
            int lastBlock = size % blocksize;

            //Debug.Print("FileSize is :" + size);
            //Debug.Print("Blocks :" + numberOfBlocks);
            //Debug.Print("LastBlock : " + lastBlock);

            byte[] buffer = new byte[size];

            byte[] header = new byte[5];

            byte[] cmd = new byte[16];
            READ_FILE.CopyTo(cmd,0);
            cmd[8] = 0;//start msb
            cmd[9] = 0;// start lsb
            cmd[10] = 0;
            cmd[11] = 0;
            cmd[12] = 4; // len msb
            cmd[13] = 0; // len lsb
            cmd[14] = 0;
            cmd[15] = 0xff;

            int recv;
            int offset = 0;
            byte blockMSB = 0;
            while (numberOfBlocks > 0)
            {
                SendCommand(cmd);
                //rx header
                 recv= m_serialPort.Read(header, 0, header.Length);
                // need to validate
                recv = m_serialPort.Read(buffer, offset, blocksize);
                offset += blocksize;
                recv = m_serialPort.Read(header, 0, header.Length);

                if (OnProgress != null)
                {
                    OnProgress.Invoke(this, new ProgressEventArg(size,blocks,numberOfBlocks));
                }
                numberOfBlocks--;
                blockMSB += 4;
                cmd[8] = blockMSB;
                


            }
            // handle last block
            if ( lastBlock > 0)
            {
                cmd[12] = (byte) (lastBlock >> 8);
                cmd[13] = (byte) (lastBlock & 0xff);

                SendCommand(cmd);
                //rx header
                recv = m_serialPort.Read(header, 0, header.Length);
                // need to validate
                recv = m_serialPort.Read(buffer, offset, lastBlock);
              
                recv = m_serialPort.Read(header, 0, header.Length);
            }
   //         Debug.Print("End capture");
            StopCapture();
    //        Debug.Print("Stop Capture");
            return buffer;
        }

        /// <summary>
        /// Read the picture filr from the camera serial port
        /// </summary>
        /// <param name="size">the number of bytes to read</param>
        /// <returns>picture file buffer</returns>
        internal byte[]  ReadFile(int size)
        {
            byte sizeMsb = (byte)(size >> 8);
            byte sizeLsb = (byte) (size & 0xff);

            byte[] rxBuffer = new byte[size + 10];

            byte[] cmd = new byte[16];
            READ_FILE.CopyTo(cmd,0);
            cmd[8] = 0;
            cmd[9] = 0;
            cmd[10] = 0;
            cmd[11] = 0;
            cmd[12] = sizeMsb;
            cmd[13] = sizeLsb;
            cmd[14] = 0;
            cmd[15] = 0x0a;

            SendCommand(cmd);

            int recv = m_serialPort.Read(rxBuffer, 0, rxBuffer.Length);

            byte[] picture = BufferToJPEG(rxBuffer);
            return picture;

        }

        private byte[] BufferToJPEG(byte[] buffer)
        {
            byte[] jpeg = new byte[buffer.Length - 10];
            int index = 5;
            for (int offset = 0; offset < jpeg.Length;offset++)
            {
                jpeg[offset] = buffer[index++];
            }

            return jpeg;
        }

        private bool SendAndWaitForResponse(byte[] cmdArray, ref byte[] readBuffer)
        {
            SendCommand(cmdArray);
            WaitForresponse(ref readBuffer, 50);

            return true;
        }

        private bool SendCommand(byte[] commandArray)
        {

            // modified for gadgettweer serial intrface
            m_serialPort.Write(commandArray);
            Thread.Sleep(10); // cant remember why this is here - investigate
            return true;

            //int len = commandArray.Length;
            //int send = m_serialPort.Write(commandArray, 0, len);
            //Thread.Sleep(10);
            //return send == len;
        }

        private bool WaitForresponse(ref byte[] readBuffer, int timeout)
        {
         //   m_serialPort.ReadTimeout = timeout;

            int recv = m_serialPort.Read(readBuffer, 0, readBuffer.Length);
            return recv != 0;
        }
    }

    
    /// <summary>
    /// Camera Image size enum
    /// vga is 640 x 480
    /// svga is 320 x 240
    /// thumb is 160 x 120
    /// </summary>
    public enum ImageSize {thumb ,svga ,vga}

 
}
